//#define HueFourier

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.Odbc;
using NumericalRecipes;
using FftLib;
using BmgNet;
using StaticUtility;

namespace ObjectLib
{
	[Serializable]
	public class LogGaborParams : ICloneable
	{
		public ColorDim Channel = ColorDim.LogLmsLum;
		public Font ImageDrawFont = new Font("Arial Narrow", 24.0f);

		public bool ConstrainFrequency=true;
		public double CenterFrequency=500.0;
		public double WidthFrequency=2.0;

		public bool ConstrainOrientation=false;
		public bool Bidirectional = true;
		public double CenterOrientation=0.0;
		public double WidthOrientation=0.5;

		public Circle WhiteningCircle;
		public bool AdaptWhite=true;
		public double AdaptWhiteFac=1.0;
		public bool Conditioned;
		/// <summary>
		/// Gives the order of derivative (negative) or integral (positive).  A zero-value indicates
		/// that the raw value is performed (no derivation or integration).
		/// </summary>
		public double Calculus = -1.0;
		/// <summary>
		/// A display parameter.
		/// </summary>
		public double Gamma=1.0;
		public bool DisplayInObjectParser=true;
		/// <summary>
		/// The number of interpolated frames to insert between this frame and the next interpolated one.
		/// </summary>
		public int NtweenAfter = 1;
		public LogGaborParams(){}
		public override string ToString()
		{
			string output = Enum.GetName(typeof(ColorDim), Channel);
			if(ConstrainOrientation)
			{
				string bid = "";
				if(Bidirectional)
					bid = "bid,";
				else
					bid = "uni,";
				output = output + "_Or("
					+ bid
					+ Math.Round(CenterOrientation/Math.PI,2).ToString() + "pi,"
					+ Math.Round(WidthOrientation/Math.PI,2).ToString() + "pi)";
			}
			if(ConstrainFrequency)
			{
				output = output + "_Freq("
					+ Math.Round(CenterFrequency,Math.Max(0,2-(int)(Math.Log10(CenterFrequency)))).ToString() + ','
					+ Math.Round(WidthFrequency,2).ToString() + "ord)";
			}
			if(Calculus>0.0)
				output = output + "_Integral(" + Math.Round(Calculus,1).ToString() + ")";
			else if(Calculus<0.0)
				output = output + "_Derivative(" + Math.Round(-Calculus,1).ToString() + ")";
			
			output = output + "_FixedGamma(" + Math.Round(Gamma,2).ToString() +")"
							+ "_Tween(" + this.NtweenAfter.ToString() + ")";

			return output;
		}
		[NonSerialized]
		public string lastSaveDir;
		/// <summary>
		/// The static method which reconstructs a new object from a serialized file.
		/// </summary>
		/// <param name="filepath"></param>
		/// <returns></returns>
		public static LogGaborParams DeserializeBinary(string filepath)
		{
			BinaryFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
			LogGaborParams obj = (LogGaborParams)formatter.Deserialize(stream);
			stream.Close();
			obj.lastSaveDir = filepath;
			int i = filepath.Length-2;
			while(i>0)
			{
				if(filepath[i]=='/' || filepath[i]=='\\')
				{
					obj.lastSaveDir = filepath.Substring(0, i);
					return obj;
				}
				i--;
			}
			return obj;
		}
		/// <summary>
		/// Constructs a new file equal to the serialization of this object.
		/// </summary>
		/// <param name="filepath"></param>
		public void SerializeBinary(string filepath)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream;
			stream = new FileStream(filepath, FileMode.Create, FileAccess.Write, FileShare.None);
			formatter.Serialize(stream,this);
			stream.Close();
		}
		public object Clone()
		{
			LogGaborParams incr = new LogGaborParams();
			incr.Channel = this.Channel;
			incr.ConstrainFrequency = this.ConstrainFrequency;
			incr.CenterFrequency = this.CenterFrequency;
			incr.WidthFrequency = this.WidthFrequency;
			incr.ConstrainOrientation = this.ConstrainOrientation;
			incr.CenterOrientation = this.CenterOrientation;
			incr.WidthOrientation = this.WidthOrientation;
			incr.Bidirectional = this.Bidirectional;
			incr.Gamma = this.Gamma;
			incr.Calculus = this.Calculus;
			incr.DisplayInObjectParser = this.DisplayInObjectParser;
			incr.lastSaveDir = this.lastSaveDir;
			incr.NtweenAfter = this.NtweenAfter;
			incr.WhiteningCircle = this.WhiteningCircle;
			incr.AdaptWhite = this.AdaptWhite;
			incr.AdaptWhiteFac = this.AdaptWhiteFac;
			incr.Conditioned = this.Conditioned;
			return incr;
		}
	}
	[Serializable]
	public class LogGaborArray : ArrayList
	{
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("baseArray", base.ToArray());
		}
		public LogGaborArray(SerializationInfo info, StreamingContext context) : base()
		{
			object[] baseArray = (object[])info.GetValue("baseArray", typeof(object[]));
			Capacity = baseArray.Length;
			AddRange(baseArray);
		}
		public LogGaborArray() : base() {}
		public void SerializeBinary(string fname)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream;
			stream = new FileStream(fname, FileMode.Create, FileAccess.Write, FileShare.None);
			formatter.Serialize( stream, this );
			stream.Close();
		}
		/// <summary>
		/// The method that opens a .LogGaborArray file from binary format.
		/// </summary>
		/// <param name="filepath"></param>
		/// <returns></returns>
		public static LogGaborArray DeserializeBinary(string filepath)
		{
			BinaryFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
			LogGaborArray obj = (LogGaborArray)formatter.Deserialize(stream);
			stream.Close();
			return obj;
		}
		/// <summary>
		/// Appends frames (including control frame a).
		/// </summary>
		/// <param name="a">The control frame and the first frame appended.  a.NtweenFrames
		/// will give the number of interpolated frames appended afterwards.</param>
		/// <param name="b">The control frame for interpolative reference.</param>
		public void AppendFrames(LogGaborParams a, LogGaborParams b)
		{
			this.Add(a);

			if(a.NtweenAfter>0)
			{
				//	Compute the increment
				double dframe = 1.0/((double)a.NtweenAfter+1.0);
				LogGaborParams incr = (LogGaborParams)a.Clone();
				if(incr.ConstrainFrequency)
				{
					incr.CenterFrequency = dframe*( Math.Log(b.CenterFrequency)-Math.Log(a.CenterFrequency));
					incr.WidthFrequency = dframe*(b.WidthFrequency-a.WidthFrequency);
				}
				else
				{
					incr.CenterFrequency = incr.WidthFrequency = 0.0;
				}
				
				if(incr.ConstrainOrientation)
				{
					incr.CenterOrientation = dframe*(b.CenterOrientation-a.CenterOrientation);
					incr.WidthOrientation = dframe*(b.WidthOrientation-a.WidthOrientation);
				}
				else
				{
					incr.CenterOrientation = incr.WidthOrientation = 0.0;
				}

				incr.Gamma = dframe*( Math.Log(b.Gamma)-Math.Log(a.Gamma) );
				incr.Calculus = dframe*(b.Calculus-a.Calculus);

				if(incr.AdaptWhite)
					incr.AdaptWhiteFac = dframe*( b.AdaptWhiteFac-a.AdaptWhiteFac );
				else
					incr.AdaptWhiteFac = 0.0;

				//	Append
				LogGaborParams p;
				double dthis;
				int i;
				for(i=0; i<a.NtweenAfter; i++)
				{
					p = (LogGaborParams)a.Clone();
					dthis = (double)(i+1);
					p.CenterFrequency = Math.Exp(Math.Log(a.CenterFrequency) + dthis*incr.CenterFrequency);
					p.CenterOrientation += dthis * incr.CenterOrientation;
					p.Calculus += dthis * incr.Calculus;
					p.Gamma = Math.Exp(Math.Log(a.Gamma) + dthis*incr.Gamma);
					p.WidthFrequency += dthis * incr.WidthFrequency;
					p.WidthOrientation += dthis * incr.WidthOrientation;
					p.AdaptWhiteFac += dthis * incr.AdaptWhiteFac;
					this.Add(p);
				}
			}
		}
		public new LogGaborParams this[int i]
		{
			get
			{
				return (LogGaborParams) base[i];
			}
			set
			{
				base[i] = value;
			}
		}
	}
	public class FilterSet
	{
		public LogGaborParams Params;
		public Ushort3 PixelValues;
		public float PixelFactor;
		public Complex2f InSpatial;
		public Complex2f InFourier;
		public Complex2f OutFourier;
		public Complex2f OutSpatial;
#if HueFourier
		public ComplexF[] HueInSpatialLeft;
		public ComplexF[] HueInSpatialRight;
#else
		/// <summary>
		/// Another filter set for use in processing Yb information, only used when Params.Channel==ColorDim.LogLmsHue.
		/// </summary>
		public FilterSet YbFilter=null;
		/// <summary>
		/// Another filter set for use in processing Rg information, only used when Params.Channel==ColorDim.LogLmsHue.
		/// </summary>
		public FilterSet RgFilter=null;
#endif
		public FftPlan2 InPlan;
		public FftPlan2 OutPlan;
		public Bitmap Result;
		private bool firstTimeThrough;
		public ObjectParserData Opd;
		public FilterSet(LogGaborParams Params, Ushort3 PixelValues, float PixelFactor, ObjectParserData Opd)
		{
			firstTimeThrough = true;
			this.Params = Params;
			this.PixelValues = PixelValues;
			this.PixelFactor = PixelFactor;
			this.Opd = Opd;
			AllocSet();
		}
		public void AllocSet()
		{
			InSpatial = new Complex2f(PixelValues.Ni, PixelValues.Nj, true);
		}
		public void InsureAllocedFourierResources()
		{
			if(InFourier==null)
			{
				InFourier = new Complex2f(PixelValues.Ni, PixelValues.Nj, true);
				InPlan = null;
			}
			if(InPlan==null)
				InPlan = new FftPlan2(InSpatial,InFourier,false);
			if(OutFourier==null)
			{
				OutFourier = new Complex2f(PixelValues.Ni, PixelValues.Nj, true);
				OutPlan = null;
			}
			if(OutSpatial==null)
			{
				OutSpatial = new Complex2f(PixelValues.Ni, PixelValues.Nj, true);
				OutPlan = null;
			}
			if(OutPlan==null)
				OutPlan = new FftPlan2(OutFourier,OutSpatial,true);
		}
		public void Go(LogGaborParams newParams)
		{
			//	Determine what needs to be done.
			bool rewhiten = firstTimeThrough && Params.AdaptWhite;
			bool refilter=true;
			double recalculus=0.0;
			bool redraw = true;

			//	Figure out what needs to be done
			if(!firstTimeThrough && newParams!=null)
			{
				rewhiten = newParams.WhiteningCircle!=null &&
					(	newParams.AdaptWhite!=Params.AdaptWhite
					|| (newParams.AdaptWhite &&
					(	newParams.AdaptWhiteFac!=Params.AdaptWhiteFac
						|| !newParams.WhiteningCircle.SameCircleAs(Params.WhiteningCircle)) )  );
				refilter = 
					newParams.ConstrainOrientation!=Params.ConstrainOrientation
					||	newParams.ConstrainFrequency!=Params.ConstrainFrequency
					|| (newParams.ConstrainOrientation && 
					(	newParams.CenterOrientation!=Params.CenterOrientation
					|| newParams.WidthOrientation!=Params.WidthOrientation
					|| newParams.Bidirectional!=Params.Bidirectional) )
					|| (newParams.ConstrainFrequency && 
					(	newParams.CenterFrequency!=Params.CenterFrequency
					|| newParams.WidthFrequency!=Params.WidthFrequency ) );

				recalculus = newParams.Calculus-Params.Calculus;

				redraw = newParams.Gamma != Params.Gamma || newParams.Channel!=Params.Channel;
			}

			//	Possible re-reading of image
			if(rewhiten && Params.AdaptWhite || firstTimeThrough)
			{
				InSpatial.CreateFrom(PixelValues, PixelFactor, Params.Channel, true);
				if(!rewhiten)
				{
					InSpatial.Center();
					InSpatial.ImposeRaisedCosineWindow(100);
				}
			}

			//	Update new parameters
			if(newParams!=null)
				Params = newParams;

			if(rewhiten)
			{
				Circle cir = Opd.ActiveCircle;
				if(cir!=null)
				{
					Params.WhiteningCircle = (Circle)cir.Clone();
					double[] eigVals;
					double[,] eigVecs, cov;
					ComplexD mean;
					InSpatial.CacheRandomParticles(cir.Center, cir.Radius, 1000);
					Params.Conditioned = InSpatial.CircleStats(cir.Center, cir.Radius, 1000, out mean, out cov, out eigVals, out eigVecs);
					//	Don't rotate, just stretch
					eigVals[0] = cov[0,0];
					eigVals[1] = cov[1,1];
					eigVecs[0,0] = eigVecs[1,1] = 1.0;
					eigVecs[0,1] = eigVecs[1,0] = 0.0;
					InSpatial.Whiten(mean, eigVals, eigVecs, Params.AdaptWhiteFac);
				}
				InSpatial.Center();
				InSpatial.ImposeRaisedCosineWindow(100);
			}

			//	See if any Fourier transform needs to be done.  Sometimes its not necessary.
			bool imageOnly =
				!Params.ConstrainOrientation && !Params.ConstrainFrequency && Params.Calculus==0.0;
			
			//	Fourier transform is necessary
			if(!imageOnly)
			{
				InsureAllocedFourierResources();

				//	Fourier is required
				InPlan.Execute();

				//	Copy Fourier image then filter
				if(refilter || rewhiten)
				{
					//	Copy
					OutFourier.CopyFrom(InFourier);

					//	Apply filter
					if(Params.ConstrainFrequency && Params.ConstrainOrientation)
					{
						OutFourier.FilterLogGabor(
							Params.CenterFrequency, Params.WidthFrequency,
							Params.CenterOrientation, Params.WidthOrientation, Params.Bidirectional, false);
					}
					else if(!Params.ConstrainFrequency && Params.ConstrainOrientation)
					{
						OutFourier.FilterLogGabor(
							double.NaN, double.NaN,
							Params.CenterOrientation, Params.WidthOrientation, Params.Bidirectional, false);
					}
					else if(Params.ConstrainFrequency && !Params.ConstrainOrientation)
					{
						OutFourier.FilterLogGabor(
							Params.CenterFrequency, Params.WidthFrequency,
							double.NaN, double.NaN, false, false);
					}
					recalculus = newParams.Calculus;
				}

				//	Apply calculus, then transform to the spatial domain
				if(refilter || recalculus!=0.0 || rewhiten)
				{
					//	Calculus
					OutFourier.CalcCalculus(recalculus);

					//	Transform to spatial domain
					OutPlan.Execute();

					//	Compute the statistics for this instance.
					OutSpatial.CalcStats();
				}
				else
				{
					//	Compute the statistics for this instance.
					OutSpatial.CalcStats();
				}
			}

			//	Create bitmap
			if(Result==null || Result.Width!=PixelValues.Nj || Result.Height!=PixelValues.Ni)
			{
				Result = new Bitmap(PixelValues.Nj, PixelValues.Ni);
				redraw = true;
			}

			Complex2f imprinter = OutSpatial;
			if(imageOnly)
				imprinter = InSpatial;

			//	Draw the result
			if(redraw || refilter || recalculus!=0.0 || imageOnly || rewhiten || firstTimeThrough)
			{
				if(Params.Channel==ColorDim.LogLmsLum || Params.Channel==ColorDim.LogLmsRg)
					imprinter.ImprintReal(Result, Params.Gamma);
				else if(Params.Channel==ColorDim.LogLmsYb)
					imprinter.ImprintImag(Result, Params.Gamma);
				else if(Params.Channel==ColorDim.LogLmsHue)
					imprinter.ImprintPhase(Result, Params.Gamma);
				else if(Params.Channel==ColorDim.LogLmsChroma)
					imprinter.ImprintAbs(Result, Params.Gamma);
				if(Params.Channel==ColorDim.LogLmsHue)
					Util.ImprintHorizontalColorWheel(Result, new RectangleF(0.0f, 0.0f, Math.Min(400.0f, Result.Width), 50.0f));
				else
					Util.ImprintHorizontalBgwyr(Result, new RectangleF(0.0f, 0.0f, Math.Min(400.0f, Result.Width), 50.0f));
				Graphics g = Graphics.FromImage(Result);
				g.DrawString(Params.ToString(), Params.ImageDrawFont, Brushes.Black, 405.0f, 2.0f, StringFormat.GenericTypographic);
			}
			firstTimeThrough = false;
		}
	}
}